package com.seguriapp.services;

import static com.google.common.collect.Lists.newArrayList;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.log4j.Logger;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ServerChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.seguriapp.configuration.PostalCodeListLoader;
import com.seguriapp.dao.AlertDao;
import com.seguriapp.dao.EntityDao;
import com.seguriapp.dao.UserDao;
import com.seguriapp.entities.Alert;
import com.seguriapp.entities.SeguriAppEntity;
import com.seguriapp.util.Channels;
import com.seguriapp.util.SearchFilterEntity;
import com.seguriapp.util.Util;

/**
 * Alert Service Implementation
 * 
 * @author Tania Gonzales (taniadgv@gmail.com)
 */
@Service
public class AlertServiceImpl implements AlertService {

  private static final Logger LOGGER = Logger.getLogger(AlertServiceImpl.class);

  @Autowired
  private AlertDao alertDao;

  @Autowired
  private UserDao userDao;

  @Autowired
  private EntityDao entityDao;

  @Inject
  private BayeuxServer bayeuxServer;

  public void setAlertDao(final AlertDao alertDao) {
    this.alertDao = alertDao;
  }

  public void setUserDao(final UserDao userDao) {
    this.userDao = userDao;
  }

  public void setEntityDao(final EntityDao entityDao) {
    this.entityDao = entityDao;
  }

  public void setBayeuxServer(final BayeuxServer bayeuxServer) {
    this.bayeuxServer = bayeuxServer;
  }

  @Transactional
  public List<Alert> listAlertsOrderByDate() {
    return alertDao.listAlertsOrderByDate();
  }

  @Transactional
  public Alert findAlertById(final Long alertId) {
    return alertDao.findById(alertId);
  }

  @Transactional
  public String saveAlert(final Alert alert) {
    try {
      if (alert == null) {
        throw new Exception();
      }
      alert.setEntity(getAlertEntity(alert));
      alertDao.save(alert);
    } catch (Exception e) {
      LOGGER.error("Error saving alert", e);
      return "Hubo un error al guardar la alerta, por favor intente nuevamente.";
    }
    sendBroadcastMessage(alert);
    return null;
  }

  @Transactional
  public List<Alert> listAlertsOrderByColumn(final SearchFilterEntity searchFilter) {
    searchFilter.setAllResults(false);
    final List<Long> entitiesIdsList = getEntitiesIdsList(searchFilter.getUserId());
    searchFilter.setIdsList(entitiesIdsList);
    final List<Alert> alertsList = alertDao.listAlertsOrderByColumn(searchFilter);
    // from the dao method we get the points inside a rectangle, here we filter
    // it inside a circle.
    if (!searchFilter.isSearchByPolStation() && searchFilter.getLatitude() != 0 && searchFilter.getLongitude() != 0
        && searchFilter.getDistance() != 0) {
      final List<Alert> newAlertsList = newArrayList();
      for (final Alert alert : alertsList) {
        if (Util.distance(alert.getLatitude(), alert.getLongitude(), searchFilter.getLatitude(), searchFilter.getLongitude(), 'K') <= searchFilter
            .getDistance()) {
          newAlertsList.add(alert);
        }
      }
      return newAlertsList;
    } else {
      return alertsList;
    }
  }

  @Transactional
  public long getAlertsTotalPages(final SearchFilterEntity searchFilter) {
    searchFilter.setAllResults(true);
    final List<Long> entitiesIdsList = getEntitiesIdsList(searchFilter.getUserId());
    searchFilter.setIdsList(entitiesIdsList);
    final List<Alert> alertsList = alertDao.listAlertsOrderByColumn(searchFilter);
    // from the dao method we get the points inside a rectangle, here we filter
    // it inside a circle.
    long totalResults = 0;
    long pages = 0;
    if (!searchFilter.isSearchByPolStation() && searchFilter.getLatitude() != 0 && searchFilter.getLongitude() != 0
        && searchFilter.getDistance() != 0) {
      long alertsListSize = 0;
      for (final Alert alert : alertsList) {
        if (Util.distance(alert.getLatitude(), alert.getLongitude(), searchFilter.getLatitude(), searchFilter.getLongitude(), 'K') <= searchFilter
            .getDistance()) {
          alertsListSize++;
        }
      }
      totalResults = alertsListSize;
    } else {
      totalResults = alertsList.size();
    }
    if (searchFilter.getPageSize() != 0) {
      pages = totalResults / searchFilter.getPageSize();
      if (totalResults % searchFilter.getPageSize() > 0) {
        pages++;
      }
    }
    return pages;
  }

  // Search in the postal code list for the entity id
  public SeguriAppEntity getAlertEntity(final Alert alert) {
    final Map<Integer, String> postalCodesList = PostalCodeListLoader.getInstance().getPostalCodeList().getPostalCodeList();
    final int postalCode = Integer.parseInt(alert.getPostalCode().substring(alert.getPostalCode().lastIndexOf(" ") + 1,
        alert.getPostalCode().length()));
    final String entityIds = postalCodesList.get(postalCode);
    if (entityIds != null && !entityIds.isEmpty()) {
      LOGGER.info("districts ids found for " + alert.getPostalCode() + ": " + entityIds);
      final String[] idsList = entityIds.split(",");
      if (idsList.length == 1) {
        return entityDao.findPoliceStationByDistrict(Long.valueOf(idsList[0]));
      }
      List<SeguriAppEntity> list = newArrayList();
      for (int i = 0; i < idsList.length; i++) {
        final SeguriAppEntity entity = entityDao.findPoliceStationByDistrict(Long.valueOf(idsList[i]));
        if (entity != null) {
          list.add(entity);
          if (alert.getAddress() != null && alert.getAddress().toLowerCase().contains(entity.getDistrict().getName().toLowerCase())) {
            return entity;
          }
        }
      }
      // TODO add behavior for not district match
    }
    return null;
  }

  private void sendBroadcastMessage(final Alert alert) {
    // send message to web client
    LOGGER.info("Sending broadcast to web client");
    final ServerChannel serverChannel = bayeuxServer.getChannel(Channels.SERVICE_NEW_ALERT);
    if (serverChannel == null) {
      LOGGER.info("Server Channel is null");
      return;
    }
    final Map<String, Object> parametros = new HashMap<String, Object>();
    parametros.put(Util.ALERT_JSON, alert.toJsonString());
    serverChannel.publish(bayeuxServer.getSession(serverChannel.getId()), parametros, null);
  }

  private List<Long> getEntitiesIdsList(final Long userId) {
    if (userId != null) {
      return userDao.isUserAdmin(userId) ? null : userDao.getUserEntities(userId);
    }
    return null;
  }

}
